package com.lagou.cl.bootstrap;

import com.lagou.cl.config.RpcProperties;
import com.lagou.cl.encode.RpcDecoder;
import com.lagou.cl.handler.AccountServiceHandler;
import com.lagou.cl.model.RpcRequest;
import com.lagou.cl.serializer.JSONSerializer;
import com.lagou.cl.util.SpringBeanUtil;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.string.StringEncoder;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @author 程  林
 * @date 2020-05-23 18:51
 * @description //
 * @since V1.0.0
 */
@Slf4j
@Component
public class ServerProducerBootStrap {

	@Autowired
	private SpringBeanUtil springBeanUtil;

	@Autowired
	private RpcProperties rpcProperties;

	@Autowired
	private CuratorFramework curatorFramework;


	public void startServer() throws InterruptedException {
		log.info("开始启动 producer ...");

		//1.创建NioEventSocketLoopGroup
		//接受请求
		EventLoopGroup bossGroup = new NioEventLoopGroup();
		//处理请求
		EventLoopGroup workerGroup = new NioEventLoopGroup();

		//2创建服务启动辅助类,装配组件
		ServerBootstrap serverBootstrap = new ServerBootstrap();
		serverBootstrap.group(bossGroup, workerGroup)
				//指定服务器端监听套接字通道NioServerSocketChannel
				.channel(NioServerSocketChannel.class)
				//设置业务职责链channelHandler组成
				//ChannelInitializer:初始化channel的channelPipelline
				.childHandler(new ChannelInitializer<SocketChannel>() {
					@Override
					protected void initChannel(SocketChannel ch) throws Exception {
						ChannelPipeline pipeline = ch.pipeline();

						//解决粘包/半包,根据消息长度自动拆包
						/**
						 *TCP粘包和黏包现象
						 *   1. TCP粘包是指发送方发送的若干个数据包到接收方时粘成一个包。从接收缓冲区来看，后一个包数据的头紧接着前一个数据的尾。
						 *   2. 当TCP连接建立后，Client发送多个报文给Server，TCP协议保证数据可靠性，但无法保证Client发了n个包，服务端也按照n个包接收。Client端发送n个数据包，Server端可能收到n-1或n+1个包。
						 *
						 *为什么出现粘包现象
						 *   1. 发送方原因: TCP默认会使用Nagle算法。而Nagle算法主要做两件事：1）只有上一个分组得到确认，才会发送下一个分组；2）收集多个小分组，在一个确认到来时一起发送。所以，正是Nagle算法造成了发送方有可能造成粘包现象。
						 *   2. 接收方原因: TCP接收方采用缓存方式读取数据包，一次性读取多个缓存中的数据包。自然出现前一个数据包的尾和后一个收据包的头粘到一起。
						 *
						 *如何解决粘包现象
						 * 1. 添加特殊符号，接收方通过这个特殊符号将接收到的数据包拆分开 - DelimiterBasedFrameDecoder特殊分隔符解码器
						 * 2. 每次发送固定长度的数据包 - FixedLengthFrameDecoder定长编码器
						 * 3. 在消息头中定义长度字段，来标识消息的总长度 - LengthFieldBasedFrameDecoder自定义长度解码器
						 */
						pipeline.addLast(new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));

						//将channelHandler添加到责任链上
						//在请求近来时和出去时候都会经过责任链
						pipeline.addLast(new RpcDecoder(RpcRequest.class, new JSONSerializer()));

						//pipeline.addLast(new RpcEncoder(RpcResponse.class, new JSONSerializer()));
						pipeline.addLast(new StringEncoder());

						pipeline.addLast(new AccountServiceHandler(curatorFramework, springBeanUtil, rpcProperties));

					}
				});

		//3 绑定端口，sync():用户阻塞当前线程，直到端口绑定完成
		ChannelFuture channelFuture = serverBootstrap.bind(rpcProperties.getHost(), rpcProperties.getPort()).sync();
		log.info("producer 注册信息：host: {} ，port: {}", rpcProperties.getHost(), rpcProperties.getPort());
		log.info("RPC Server start success ...");

		//应用程序阻塞，直到应用程序关闭
		channelFuture.channel().closeFuture();
	}
}
